package kubernetes

import (
	"bytes"
	"context"

	appsv1 "k8s.io/api/apps/v1"
	batchv1 "k8s.io/api/batch/v1"
	batchv1beta1 "k8s.io/api/batch/v1beta1"
	corev1 "k8s.io/api/core/v1"
	networkingv1 "k8s.io/api/networking/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/client-go/kubernetes/scheme"
)

func (in *FcClient) GetConfigMap(namespace, configName string) (*corev1.ConfigMap, error) {
	configMap, err := in.k8s.CoreV1().ConfigMaps(namespace).Get(context.TODO(), configName, emptyGetOptions)
	if err != nil {
		return &corev1.ConfigMap{}, err
	}

	return configMap, nil
}

// GetNamespace fetches and returns the specified namespace definition
// from the cluster
func (in *FcClient) GetNamespace(namespace string) (*corev1.Namespace, error) {
	ns, err := in.k8s.CoreV1().Namespaces().Get(context.TODO(), namespace, emptyGetOptions)
	if err != nil {
		return &corev1.Namespace{}, err
	}

	return ns, nil
}

// GetNamespaces returns a list of all namespaces of the cluster.
// It returns a list of all namespaces of the cluster.
// It returns an error on any problem.
func (in *FcClient) GetNamespaces(labelSelector string) ([]corev1.Namespace, error) {
	var listOptions metav1.ListOptions

	// Apply labelSelector filtering if specified
	if labelSelector != "" {
		listOptions = metav1.ListOptions{LabelSelector: labelSelector}
	} else {
		listOptions = emptyListOptions
	}

	namespaces, err := in.k8s.CoreV1().Namespaces().List(context.TODO(), listOptions)
	if err != nil {
		return nil, err
	}

	return namespaces.Items, nil
}

// GetServices returns a list of services for a given namespace.
// If selectorLabels is defined the list of services is filtered for those that matches Services selector labels.
// It returns an error on any problem.
func (in *FcClient) GetServices(namespace string, selectorLabels map[string]string) ([]corev1.Service, error) {
	var allServices []corev1.Service

	if allServicesList, err := in.k8s.CoreV1().Services(namespace).List(context.TODO(), emptyListOptions); err == nil {
		allServices = allServicesList.Items
	} else {
		return []corev1.Service{}, err
	}

	if selectorLabels == nil {
		return allServices, nil
	}
	var services []corev1.Service
	for _, svc := range allServices {
		svcSelector := labels.Set(svc.Spec.Selector).AsSelector()
		if !svcSelector.Empty() && svcSelector.Matches(labels.Set(selectorLabels)) {
			services = append(services, svc)
		}
	}
	return services, nil
}

// GetDeployment returns the definition of a specific deployment.
// It returns an error on any problem.
func (in *FcClient) GetDeployment(namespace, deploymentName string) (*appsv1.Deployment, error) {
	return in.k8s.AppsV1().Deployments(namespace).Get(context.TODO(), deploymentName, emptyGetOptions)
}

// GetDeployments returns an array of deployments for a given namespace.
// It returns an error on any problem.
func (in *FcClient) GetDeployments(namespace string) ([]appsv1.Deployment, error) {
	if depList, err := in.k8s.AppsV1().Deployments(namespace).List(context.TODO(), emptyListOptions); err == nil {
		return depList.Items, nil
	} else {
		return []appsv1.Deployment{}, err
	}
}

// GetDeployments returns an array of deployments for a given namespace and a set of labels.
// An empty labelSelector will fetch all Deployments for a namespace.
// It returns an error on any problem.
func (in *FcClient) GetDeploymentsByLabel(namespace string, labelSelector string) ([]appsv1.Deployment, error) {
	listOptions := metav1.ListOptions{LabelSelector: labelSelector}
	if depList, err := in.k8s.AppsV1().Deployments(namespace).List(context.TODO(), listOptions); err == nil {
		return depList.Items, nil
	} else {
		return []appsv1.Deployment{}, err
	}
}

func (in *FcClient) GetReplicaSets(namespace string) ([]appsv1.ReplicaSet, error) {
	if rsList, err := in.k8s.AppsV1().ReplicaSets(namespace).List(context.TODO(), emptyListOptions); err == nil {
		return rsList.Items, nil
	} else {
		return []appsv1.ReplicaSet{}, err
	}
}

func (in *FcClient) GetStatefulSet(namespace string, statefulsetName string) (*appsv1.StatefulSet, error) {
	return in.k8s.AppsV1().StatefulSets(namespace).Get(context.TODO(), statefulsetName, emptyGetOptions)
}

func (in *FcClient) GetStatefulSets(namespace string) ([]appsv1.StatefulSet, error) {
	if ssList, err := in.k8s.AppsV1().StatefulSets(namespace).List(context.TODO(), emptyListOptions); err == nil {
		return ssList.Items, nil
	} else {
		return []appsv1.StatefulSet{}, err
	}
}

func (in *FcClient) GetReplicationControllers(namespace string) ([]corev1.ReplicationController, error) {
	if rcList, err := in.k8s.CoreV1().ReplicationControllers(namespace).List(context.TODO(), emptyListOptions); err == nil {
		return rcList.Items, nil
	} else {
		return []corev1.ReplicationController{}, err
	}
}

// GetService returns the definition of a specific service.
// It returns an error on any problem.
func (in *FcClient) GetService(namespace, serviceName string) (*corev1.Service, error) {
	return in.k8s.CoreV1().Services(namespace).Get(context.TODO(), serviceName, emptyGetOptions)
}

func (in *FcClient) CreateService(namespace string, service *corev1.Service) (*corev1.Service, error) {
	return in.k8s.CoreV1().Services(namespace).Create(context.TODO(), service, emptyCreateOptions)
}

func (in *FcClient) UpdateService(namespace string, service *corev1.Service) (*corev1.Service, error) {
	return in.k8s.CoreV1().Services(namespace).Update(context.TODO(), service, emptyUpdateOptions)
}

func (in *FcClient) DeleteService(namespace, serviceName string) error {
	return in.k8s.CoreV1().Services(namespace).Delete(context.TODO(), serviceName, emptyDeleteOptions)
}

// GetEndpoints return the list of endpoint of a specific service.
// It returns an error on any problem.
func (in *FcClient) GetEndpoints(namespace, serviceName string) (*corev1.Endpoints, error) {
	return in.k8s.CoreV1().Endpoints(namespace).Get(context.TODO(), serviceName, emptyGetOptions)
}

// GetPods returns the pods definitions for a given set of labels.
// An empty labelSelector will fetch all pods found per a namespace.
// It returns an error on any problem.
func (in *FcClient) GetPods(namespace, labelSelector string) ([]corev1.Pod, error) {
	// An empty selector is ambiguous in the go client, could mean either "select all" or "select none"
	// Here we assume empty == select all
	// (see also https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors)
	if pods, err := in.k8s.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector}); err == nil {
		return pods.Items, nil
	} else {
		return []corev1.Pod{}, err
	}
}

// GetPod returns the pod definitions for a given pod name.
// It returns an error on any problem.
func (in *FcClient) GetPod(namespace, name string) (*corev1.Pod, error) {
	if pod, err := in.k8s.CoreV1().Pods(namespace).Get(context.TODO(), name, emptyGetOptions); err != nil {
		return nil, err
	} else {
		return pod, nil
	}
}

// GetPod returns the pod definitions for a given pod name.
// It returns an error on any problem.
func (in *FcClient) GetPodLogs(namespace, name string, opts *corev1.PodLogOptions) (*PodLogs, error) {
	req := in.k8s.CoreV1().RESTClient().Get().Namespace(namespace).Name(name).Resource("pods").SubResource("log").VersionedParams(opts, scheme.ParameterCodec)

	readCloser, err := req.Stream(context.TODO())
	if err != nil {
		return nil, err
	}

	defer func() {
		_ = readCloser.Close()
	}()
	buf := new(bytes.Buffer)
	_, err = buf.ReadFrom(readCloser)
	if err != nil {
		return nil, err
	}

	return &PodLogs{Logs: buf.String()}, nil
}

func (in *FcClient) GetCronJobs(namespace string) ([]batchv1beta1.CronJob, error) {
	if cjList, err := in.k8s.BatchV1beta1().CronJobs(namespace).List(context.TODO(), emptyListOptions); err == nil {
		return cjList.Items, nil
	} else {
		return []batchv1beta1.CronJob{}, err
	}
}

func (in *FcClient) GetJobs(namespace string) ([]batchv1.Job, error) {
	if jList, err := in.k8s.BatchV1().Jobs(namespace).List(context.TODO(), emptyListOptions); err == nil {
		return jList.Items, nil
	} else {
		return []batchv1.Job{}, err
	}
}

func (in *FcClient) CreateNamespace(namespace *corev1.Namespace) (*corev1.Namespace, error) {
	return in.k8s.CoreV1().Namespaces().Create(context.TODO(), namespace, emptyCreateOptions)
}

func (in *FcClient) DeleteNamespace(namespace string) error {
	return in.k8s.CoreV1().Namespaces().Delete(context.TODO(), namespace, emptyDeleteOptions)
}

func (in *FcClient) CreateNetworkPolicy(namespace string, policy *networkingv1.NetworkPolicy) (*networkingv1.NetworkPolicy, error) {
	return in.k8s.NetworkingV1().NetworkPolicies(namespace).Create(context.TODO(), policy, emptyCreateOptions)
}

func (in *FcClient) DeleteNetworkPolicy(namespace string, policyName string) error {
	return in.k8s.NetworkingV1().NetworkPolicies(namespace).Delete(context.TODO(), policyName, emptyDeleteOptions)
}

func (in *FcClient) GetNetworkPolicy(namespace string, policyName string) (*networkingv1.NetworkPolicy, error) {
	return in.k8s.NetworkingV1().NetworkPolicies(namespace).Get(context.TODO(), policyName, emptyGetOptions)
}

func (in *FcClient) GetSecrets(namespace string) (*corev1.SecretList, error) {
	return in.k8s.CoreV1().Secrets(namespace).List(context.TODO(), emptyListOptions)
}

func (in *FcClient) GetSecret(namespace string, secretName string) (*corev1.Secret, error) {
	return in.k8s.CoreV1().Secrets(namespace).Get(context.TODO(), secretName, emptyGetOptions)
}

func (in *FcClient) CreateSecret(namespace string, secret *corev1.Secret) (*corev1.Secret, error) {
	return in.k8s.CoreV1().Secrets(namespace).Create(context.TODO(), secret, emptyCreateOptions)
}

func (in *FcClient) UpdateSecret(namespace string, secret *corev1.Secret) (*corev1.Secret, error) {
	return in.k8s.CoreV1().Secrets(namespace).Update(context.TODO(), secret, emptyUpdateOptions)
}

func (in *FcClient) UpdateConfigMap(namespace string, configMap *corev1.ConfigMap) (*corev1.ConfigMap, error) {
	return in.k8s.CoreV1().ConfigMaps(namespace).Update(context.TODO(), configMap, emptyUpdateOptions)
}

func (in *FcClient) DeleteSecret(namespace, secretName string) error {
	return in.k8s.CoreV1().Secrets(namespace).Delete(context.TODO(), secretName, emptyDeleteOptions)
}

func (in *FcClient) GetIngress(namespace string, ingressName string) (*networkingv1.Ingress, error) {
	return in.k8s.NetworkingV1().Ingresses(namespace).Get(context.TODO(), ingressName, emptyGetOptions)
}

func (in *FcClient) CreateIngress(namespace string, ingress *networkingv1.Ingress) (*networkingv1.Ingress, error) {
	return in.k8s.NetworkingV1().Ingresses(namespace).Create(context.TODO(), ingress, emptyCreateOptions)
}

func (in *FcClient) UpdateIngress(namespace string, ingress *networkingv1.Ingress) (*networkingv1.Ingress, error) {
	return in.k8s.NetworkingV1().Ingresses(namespace).Update(context.TODO(), ingress, emptyUpdateOptions)
}

func (in *FcClient) GetNodes(labelSelector string) (*corev1.NodeList, error) {
	return in.k8s.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector})
}
